use std::time::Duration;

/// Shows the runtime of a function or a closure.
///
/// Input a function or a closure and a name as a mark.
/// The closure input can be Fn, FnMut, FnOnce.
///
/// # Examples
///
/// ```rust
/// use meth_tools::prelude::timeit;
/// timeit("sleep 5s", || {
///     std::thread::sleep(std::time::Duration::new(5,0));
/// });
/// ```
/// This function will show a output likes:
/// ```no run
/// sleep 5s started
/// sleep 5s ended
/// sleep 5s takes 5.000s
/// ```
#[allow(unused)]
pub fn timeit(name: &str, f: impl FnOnce() -> ()) {
    println!("{name} started");
    let start = std::time::Instant::now();
    std::hint::black_box(f());
    let end = std::time::Instant::now();
    println!("{name} ended");

    let nano = end.duration_since(start).as_nanos();
    let (time, unit) = if nano > 1_000_000_000 {
        (format!("{:.3}", nano as f64 / 1e9), "s")
    } else if nano > 1_000_000 {
        (format!("{:.3}", nano as f64 / 1e6), "ms")
    } else if nano > 1_000 {
        (format!("{:.3}", nano as f64 / 1e3), "us")
    } else {
        (format!("{}", nano), "ns")
    };
    println!("{name} takes {time}{unit}\n");
}

/// Shows the runtime of a function or a closure.
///
/// Input a function or a closure and a name as a mark.
/// Input a number as a repeat counter.
/// The closure input cannot be FnOnce because FnOnce cannot repeat.
///
/// # Examples
///
/// ```rust
/// use meth_tools::prelude::timeit_repeat;
/// timeit_repeat("sleep 5s", || {
///     std::thread::sleep(std::time::Duration::new(1,0));
/// }, 5);
/// ```
/// This function will show a output likes:
/// ```txt
/// sleep 5s started
/// sleep 5s ended
/// sleep 5s takes 5.000s
/// ```
///
/// Note that some functions change in complexity under loops.
/// In particular, the complexity of the sorting function is greatly reduced after the first loop.
///
/// # Examples
///
/// ```rust
/// use meth_tools::prelude::timeit_repeat;
/// let mut v = vec![ 1, 4, 2, 8, 5, 7];
/// timeit_repeat("sort", || { v.sort() }, 10);
/// ```
/// In this example, the time taken to cycle 1 and 10 times is almost the same.
#[allow(unused)]
pub fn timeit_repeat(name: &str, mut f: impl FnMut() -> (), repeat: usize) {
    println!("{name} started");
    let start = std::time::Instant::now();
    for _ in 0..repeat {
        std::hint::black_box(f());
    }
    let end = std::time::Instant::now();
    println!("{name} ended");

    let nano = end.duration_since(start).as_nanos();
    let (time, unit) = if nano > 1_000_000_000 {
        (format!("{:.3}", nano as f64 / 1e9), "s")
    } else if nano > 1_000_000 {
        (format!("{:.3}", nano as f64 / 1e6), "ms")
    } else if nano > 1_000 {
        (format!("{:.3}", nano as f64 / 1e3), "us")
    } else {
        (format!("{}", nano), "ns")
    };
    println!("{name} takes {time}{unit}\n");
}

/// Returns the runtime of a function or a closure.
///
/// Input a function or a closure.
/// The closure input can be Fn, FnMut, FnOnce.
///
/// # Examples
///
/// ```rust
/// use meth_tools::prelude::timeit_silent;
/// let runtime = timeit_silent(|| {
///     std::thread::sleep(std::time::Duration::new(5,0));
/// }).as_nanos();
/// println!("{runtime}");
/// ```
#[allow(unused)]
pub fn timeit_silent(f: impl FnOnce() -> ()) -> std::time::Duration {
    let start = std::time::Instant::now();
    std::hint::black_box(f());
    let end = std::time::Instant::now();
    end.duration_since(start)
}

/// Returns the runtime of a function or a closure.
///
/// Input a function or a closure.
/// Input a number as a repeat counter.
/// The closure input cannot be FnOnce because FnOnce cannot repeat.
///
/// # Examples
///
/// ```rust
/// use meth_tools::prelude::timeit_silent_repeat;
/// let runtime = timeit_silent_repeat(|| {
///     std::thread::sleep(std::time::Duration::new(1,0));
/// }, 5).as_nanos();
/// println!("{runtime}");
/// ```
#[allow(unused)]
pub fn timeit_silent_repeat(f: impl Fn() -> (), repeat: usize) -> std::time::Duration {
    let start = std::time::Instant::now();
    for _ in 0..repeat {
        std::hint::black_box(f());
    }
    let end = std::time::Instant::now();
    end.duration_since(start)
}

/// Shows the runtime of a function or a closure.
///
/// Input a function or a closure and followed by its arguments.
///
/// # Examples
///
/// ```rust
/// use meth_tools::timeit;
/// timeit!();
/// timeit!(String::new);
/// timeit!(String::new, ());
///
/// timeit!(|| {});
/// timeit!(|x:i32, y:i32| x + y, 2, 4);
/// timeit!(|x:i32, y:i32| x + y, (2, 4));
///
/// timeit!(i32::wrapping_add, 1, 2);
/// timeit!(i32::wrapping_add, (1, 2));
///
/// timeit!(std::thread::sleep, std::time::Duration::new(5 ,0));
/// timeit!(std::thread::sleep, (std::time::Duration::new(5 ,0)));
/// ```
#[macro_export]
macro_rules! timeit {
    () => {};
    ($func:expr$(,)?) => {{
        let name = $crate::prelude::type_of(&$func);
        println!("{name} started");
        let start = std::time::Instant::now();
        std::hint::black_box($func());
        let end = std::time::Instant::now();
        println!("{name} ended");

        let nano = end.duration_since(start).as_nanos();
        let (time, unit) = if nano > 1_000_000_000 {
            (format!("{:.3}", nano as f64 / 1e9), "s")
        } else if nano > 1_000_000 {
            (format!("{:.3}", nano as f64 / 1e6), "ms")
        } else if nano > 1_000 {
            (format!("{:.3}", nano as f64 / 1e3), "us")
        } else {
            (format!("{}", nano), "ns")
        };
        println!("{name} takes {time}{unit}\n");
    }};
    ($func:expr, ($($arg:expr), *$(,)?)$(,)?) => {{
        let name = $crate::prelude::type_of(&$func);
        println!("{name} started");
        let start = std::time::Instant::now();
        std::hint::black_box($func($($arg),*));
        let end = std::time::Instant::now();
        println!("{name} ended");

        let nano = end.duration_since(start).as_nanos();
        let (time, unit) = if nano > 1_000_000_000 {
            (format!("{:.3}", nano as f64 / 1e9), "s")
        } else if nano > 1_000_000 {
            (format!("{:.3}", nano as f64 / 1e6), "ms")
        } else if nano > 1_000 {
            (format!("{:.3}", nano as f64 / 1e6), "us")
        } else {
            (format!("{:.3}", nano), "ns")
        };
        println!("{name} takes {time}{unit}\n");
    }};
    ($func:expr, $($arg:expr),*$(,)?) => {{
        let name = $crate::prelude::type_of(&$func);
        println!("{name} started");
        let start = std::time::Instant::now();
        std::hint::black_box($func($($arg),*));
        let end = std::time::Instant::now();
        println!("{name} ended");

        let nano = end.duration_since(start).as_nanos();
        let (time, unit) = if nano > 1_000_000_000 {
            (format!("{:.3}", nano as f64 / 1e9), "s")
        } else if nano > 1_000_000 {
            (format!("{:.3}", nano as f64 / 1e6), "ms")
        } else if nano > 1_000 {
            (format!("{:.3}", nano as f64 / 1e3), "us")
        } else {
            (format!("{}", nano), "ns")
        };
        println!("{name} takes {time}{unit}\n");
    }};
}

/// Shows the runtime of a function or a closure.
///
/// Input a function or a closure and followed by its arguments.
/// Input a number as a repeat counter separated by semicolons.
/// The closure input cannot be FnOnce because FnOnce cannot repeat.
///
/// # Examples
/// ```rust
/// use meth_tools::timeit_repeat;
/// timeit_repeat!();
///
/// timeit_repeat!(|| {}; 3);
/// timeit_repeat!(|x:i32, y:i32| x + y, 2, 4; 3);
/// timeit_repeat!(|x:i32, y:i32| x + y, (2, 4); 3);
///
/// timeit_repeat!(i32::wrapping_sub, 5, 3; 4);
/// timeit_repeat!(i32::wrapping_sub, (5, 3); 4);
///
/// timeit_repeat!(std::thread::sleep, std::time::Duration::new(1 ,0); 5);
/// timeit_repeat!(std::thread::sleep, (std::time::Duration::new(1 ,0)); 5);
/// ```
#[macro_export]
macro_rules! timeit_repeat {
    () => {};
    ($func:expr; $repeat:expr$(,)?) => {{
        let name = $crate::prelude::type_of(&$func);
        println!("{name} started");
        let start = std::time::Instant::now();
        for _ in 0..$repeat {
            std::hint::black_box($func());
        }
        let end = std::time::Instant::now();
        println!("{name} ended");

        let nano = end.duration_since(start).as_nanos();
        let (time, unit) = if nano > 1_000_000_000 {
            (format!("{:.3}", nano as f64 / 1e9), "s")
        } else if nano > 1_000_000 {
            (format!("{:.3}", nano as f64 / 1e6), "ms")
        } else if nano > 1_000 {
            (format!("{:.3}", nano as f64 / 1e3), "us")
        } else {
            (format!("{}", nano), "ns")
        };
        println!("{name} takes {time}{unit}\n");
    }};
    ($func:expr, ($($arg:expr), *$(,)?); $repeat:expr$(,)?) => {{
        let name = $crate::prelude::type_of(&$func);
        println!("{name} started");
        let start = std::time::Instant::now();
        for _ in 0..$repeat {
            std::hint::black_box($func($($arg),*));
        }
        let end = std::time::Instant::now();
        println!("{name} ended");

        let nano = end.duration_since(start).as_nanos();
        let (time, unit) = if nano > 1_000_000_000 {
            (format!("{:.3}", nano as f64 / 1e9), "s")
        } else if nano > 1_000_000 {
            (format!("{:.3}", nano as f64 / 1e6), "ms")
        } else if nano > 1_000 {
            (format!("{:.3}", nano as f64 / 1e6), "us")
        } else {
            (format!("{:.3}", nano), "ns")
        };
        println!("{name} takes {time}{unit}\n");
    }};
    ($func:expr, $($arg:expr),*; $repeat:expr$(,)?) => {{
        let name = $crate::prelude::type_of(&$func);
        println!("{name} started");
        let start = std::time::Instant::now();
        for _ in 0..$repeat {
            std::hint::black_box($func($($arg),*));
        }
        let end = std::time::Instant::now();
        println!("{name} ended");

        let nano = end.duration_since(start).as_nanos();
        let (time, unit) = if nano > 1_000_000_000 {
            (format!("{:.3}", nano as f64 / 1e9), "s")
        } else if nano > 1_000_000 {
            (format!("{:.3}", nano as f64 / 1e6), "ms")
        } else if nano > 1_000 {
            (format!("{:.3}", nano as f64 / 1e3), "us")
        } else {
            (format!("{}", nano), "ns")
        };
        println!("{name} takes {time}{unit}\n");
    }};
}

/// Returns the runtime of a function or a closure.
///
/// Input a function or a closure and followed by its arguments.
///
/// # Examples
/// ```rust
/// use meth_tools::timeit_silent;
/// timeit_silent!();
/// timeit_silent!(String::new);
/// timeit_silent!(String::new, ());
///
/// timeit_silent!(|| {});
/// timeit_silent!(|x:i32, y:i32| x + y, 2, 4);
/// timeit_silent!(|x:i32, y:i32| x + y, (2, 4));
///
/// timeit_silent!(i32::wrapping_add, 1, 2);
/// timeit_silent!(i32::wrapping_add, (1, 2));
///
/// timeit_silent!(std::thread::sleep, std::time::Duration::new(5 ,0));
/// timeit_silent!(std::thread::sleep, (std::time::Duration::new(5 ,0)));
/// ```
#[macro_export]
macro_rules! timeit_silent {
    () => { std::time::Duration::new(0, 0) };
    ($func:expr$(,)?) => {{
        let start = std::time::Instant::now();
        std::hint::black_box($func());
        let end = std::time::Instant::now();

        end.duration_since(start)
    }};
    ($func:expr, ($($arg:expr), *$(,)?)$(,)?) => {{
        let start = std::time::Instant::now();
        std::hint::black_box($func($($arg),*));
        let end = std::time::Instant::now();

        end.duration_since(start)
    }};
    ($func:expr, $($arg:expr),* $(,)?) => {{
        let start = std::time::Instant::now();
        std::hint::black_box($func($($arg),*));
        let end = std::time::Instant::now();

        end.duration_since(start)
    }};
}

/// Returns the runtime of a function or a closure.
///
/// Input a function or a closure and followed by its arguments.
/// Input a number as a repeat counter separated by semicolons.
/// The closure input cannot be FnOnce because FnOnce cannot repeat.
///
/// # Examples
/// ```rust
/// use meth_tools::timeit_silent_repeat;
/// timeit_silent_repeat!();
///
/// timeit_silent_repeat!(|| {}; 3);
/// timeit_silent_repeat!(|x:i32, y:i32| x + y, 2, 4; 3);
/// timeit_silent_repeat!(|x:i32, y:i32| x + y, (2, 4); 3);
///
/// timeit_silent_repeat!(i32::wrapping_sub, 5, 3; 4);
/// timeit_silent_repeat!(i32::wrapping_sub, (5, 3); 4);
///
/// timeit_silent_repeat!(std::thread::sleep, std::time::Duration::new(1 ,0); 5);
/// timeit_silent_repeat!(std::thread::sleep, (std::time::Duration::new(1 ,0)); 5);
/// ```
#[macro_export]
macro_rules! timeit_silent_repeat {
    () => { std::time::Duration::new(0, 0) };
    ($func:expr; $repeat:expr$(,)?) => {{
        let start = std::time::Instant::now();
        for _ in 0..$repeat {
            std::hint::black_box($func());
        }
        let end = std::time::Instant::now();
        end.duration_since(start)
    }};
    ($func:expr, ($($arg:expr), *$(,)?); $repeat:expr$(,)?) => {{
        let start = std::time::Instant::now();
        for _ in 0..$repeat {
            std::hint::black_box($func($($arg),*));
        }
        let end = std::time::Instant::now();

        end.duration_since(start)
    }};
    ($func:expr, $($arg:expr),*; $repeat:expr$(,)?) => {{
        let start = std::time::Instant::now();
        for _ in 0..$repeat {
            std::hint::black_box($func($($arg),*));
        }
        let end = std::time::Instant::now();
        end.duration_since(start)
    }};
}

/// Sleeps for a specific number of seconds
///
/// # Example
///
/// ```rust
/// use meth_tools::prelude::wait_secs;
/// wait_secs(3);
/// ```
#[allow(unused)]
#[inline]
pub fn wait_secs(secs: u64) {
    std::thread::sleep(Duration::from_secs(secs));
}

/// Sleeps for a specific number of milliseconds
///
/// # Example
///
/// ```rust
/// use meth_tools::prelude::wait_millis;
/// wait_millis(3_000);
/// ```
#[allow(unused)]
#[inline]
pub fn wait_millis(millis: u64) {
    std::thread::sleep(Duration::from_millis(millis));
}

/// Sleeps for a specific number of microseconds
///
/// # Example
///
/// ```rust
/// use meth_tools::prelude::wait_micros;
/// wait_micros(3_000_000);
/// ```
#[allow(unused)]
#[inline]
pub fn wait_micros(micros: u64) {
    std::thread::sleep(Duration::from_micros(micros));
}

/// Sleeps for a specific number of nanoseconds
///
/// # Example
///
/// ```rust
/// use meth_tools::prelude::wait_nanos;
/// wait_nanos(3_000_000_000);
/// ```
#[allow(unused)]
#[inline]
pub fn wait_nanos(nanos: u64) {
    std::thread::sleep(Duration::from_nanos(nanos));
}
